#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <termios.h>
#include "model.h"
#include "z_logger.h"

static speed_t get_serial_speed(int baud_rate) {
    switch (baud_rate) {
        case 110:
            return B110;
        case 134:
            return B134;
        case 150:
            return B150;
        case 200:
            return B200;
        case 300:
            return B300;
        case 600:
            return B600;
        case 1200:
            return B1200;
        case 2400:
            return B2400;
        case 4800:
            return B4800;
        case 9600:
        default:
            return B9600;
        case 19200:
            return B19200;
        case 38400:
            return B38400;
        case 57600:
            return B57600;
    }
}

/* Set the baud rate */
int set_baud_rate(struct termios *tios, int baud_rate) {
    if (NULL == tios) return -1;

    speed_t speed = get_serial_speed(baud_rate);
    int r1 = cfsetispeed(tios, speed);
    int r2 = cfsetospeed(tios, speed);

    return r1 | r2;
}

int set_data_bits(struct termios *tios, int data_bits) {
    if (NULL == tios) return -1;

    tios->c_cflag &= ~CSIZE;
    switch (data_bits) {
        case 5:
            tios->c_cflag |= CS5;
            break;
        case 6:
            tios->c_cflag |= CS6;
            break;
        case 7:
            tios->c_cflag |= CS7;
            break;
        case 8:
        default:
            tios->c_cflag |= CS8;
            break;
    }
    return 0;
}

int set_stop_bits(struct termios *tios, int stop_bits) {
    if (NULL == tios) return -1;

    switch (stop_bits) {
        case 1:
            tios->c_cflag &= ~CSTOPB;
            break;
        default:
            tios->c_cflag |= CSTOPB;
            break;
    }
    return 0;
}

int set_parity(struct termios *tios, int parity) {
    if (NULL == tios) return -1;

    switch (parity) {
        case 0:
        default:
            tios->c_cflag &= ~PARENB;
            break;
        case 1:
            tios->c_cflag |= PARENB;
            tios->c_cflag |= PARODD;
            break;
        case 2:
            tios->c_cflag |= PARENB;
            tios->c_cflag &= ~PARODD;
            break;
    }

    //enable parity check
    switch (parity) {
        case 0:
            tios->c_iflag &= ~INPCK;
            break;
        default:
            tios->c_iflag |= INPCK;
            break;
    }

    return 0;
}

int open_serial(const char *device, int baud_rate, int parity, int data_bits, int stop_bits) {
    int fd = open(device, O_RDWR | O_NOCTTY | O_NDELAY | O_EXCL);
    if (-1 == fd) {
        log_error("can't open device %s", device);
        return -1;
    }

    struct termios tios;
    memset(&tios, 0, sizeof(struct termios));

    set_baud_rate(&tios, baud_rate);
    set_data_bits(&tios, data_bits);
    set_stop_bits(&tios, stop_bits);
    set_parity(&tios, parity);

    tios.c_cflag |= (CREAD | CLOCAL);
    //Software flow control is disabled
    tios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

    tios.c_iflag &= ~(IXON | IXOFF | IXANY);
    //Postprocess output (not set = raw output)
    tios.c_oflag &= ~OPOST;
    //Minimum number of characters to read
    tios.c_cc[VMIN] = 0;
    //Time to wait for data (tenths of seconds)
    tios.c_cc[VTIME] = 0;

    if (tcsetattr(fd, TCSANOW, &tios) < 0) {
        close(fd);
        return -1;
    }

    return fd;
}

int channel_open_serial(channel_t *channel) {
    if (!channel_is_serial(channel->type)) {
        log_error("can't open serial(%s) type %d", channel->serial.device, channel->type);
        return false;
    }

    serial_params_t *serial = &channel->serial;
    channel->fd = open_serial(serial->device, serial->baud, serial->parity, serial->data_bits, serial->stop_bits);

    return channel->fd;
}

void channel_close(channel_t *channel) {
    if (-1 != channel->fd) {
        close(channel->fd);
    }
    channel->fd = -1;
}

ssize_t read_fully(int fd, void *ptr, size_t nbytes, int timeout) {
    void *ps = ptr, *pe = ps + nbytes;
    time_t ts = time(NULL);

    while (ps < pe) {
        ssize_t rc = read(fd, ps, pe - ps);
        if (rc > 0) {
            ps += rc;
        } else if (0 == rc || (-1 == rc && EAGAIN == errno)) {
            usleep(50 * 1000);
            if (time(NULL) - ts > timeout) {
                return rc;
            }
        } else {
            return rc;
        }
    }

    return pe - ps;
}

ssize_t write_fully(int fd, const void *ptr, size_t nbytes, int timeout) {
    const void *ps = ptr, *pe = ps + nbytes;
    time_t ts = time(NULL);

    while (ps < pe) {
        ssize_t rc = write(fd, ps, pe - ps);
        if (rc > 0) {
            ps += rc;
        } else if (0 == rc || (-1 == rc && EAGAIN == errno)) {
            usleep(50 * 1000);
            if (time(NULL) - ts > timeout) {
                return rc;
            }
        } else {
            return rc;
        }
    }

    return pe - ps;
}

ssize_t channel_read(channel_t *channel, void *ptr, size_t nbytes) {
    return read_fully(channel->fd, ptr, nbytes, channel->timeout);
}

ssize_t channel_write(channel_t *channel, const void *ptr, size_t nbytes) {
    return write_fully(channel->fd, ptr, nbytes, channel->timeout);
}

